#include "modules/buffer_mgr.h"

BufferManager * BufferManager::bm = NULL;

BufferManager::BufferManager(int block_count) :  buffer_size(BLOCK_SIZE), num_blocks(block_count)
{
	BufferInfo *head = NULL;
	BufferInfo *cur = NULL;

	for(int i=0; i<block_count; i++)
	{
		BufferInfo *temp = new BufferInfo;
		temp->buffer = new BYTE[buffer_size];
		temp->prev = cur;
		if(cur != NULL)
		{
			cur->next = temp;
		}
		temp->prev = cur;
		if(head == NULL)
		{
			head = temp;
		}
		cur = temp;
	}

	cur->next = head;
	head->prev = cur;
	mru=head;

	//Reset the counters
	req_count = 0;
	miss_count = 0;

}

BufferManager::~BufferManager()
{
	BufferInfo *cur = mru;
	while(1)
	{
		BufferInfo *temp = cur;
		cur = cur->next;
		//Write the Blocks if they were in use
		if(temp->isdirty)
		{
			string tblname(temp->tablename);
			write_file(tblname, temp->block_num, temp->buffer);
		}

		delete [] temp->buffer;
		delete temp;

		if(cur == mru)
		{
			break;
		}
	}
}

void BufferManager::create_table(string &tblname)
{
	//create file
	int fd = open(dbname(tblname).c_str(), O_CREAT | O_EXCL, S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP |S_IROTH | S_IWOTH );
	if(fd < 0)
	{
		throw Exception(buffermgr_createtbl_failed);
	}
	external_commit(tblname, 0xFFFF);
	close(fd);
}

void BufferManager::read_block(string &tblname, UINT32 block, BYTE *buffer)
{
	BufferInfo * node = load_block(tblname, block,IS_READ);
	memcpy(buffer, node->buffer, buffer_size);
}

void BufferManager::write_block(string &tblname, UINT32 block, BYTE *buffer)
{
	BufferInfo *node = load_block(tblname, block,IS_WRITE);
	memcpy(node->buffer, buffer, buffer_size);
	node->isdirty = true;
}

void BufferManager::commit()
{
	//Go through the link list and write all the blocks
	//Reset the Dirty Bit
	for(UINT32 i=0; i<num_blocks; i++)
	{
		BufferInfo *node = mru; 

		if(node->isdirty)
		{
			string tblname(node->tablename);
			write_file(tblname, node->block_num, node->buffer);
		}

		node->isdirty = false;

		node = node->next;
	}

	return;
}

BufferInfo *BufferManager::load_block(string &tblname, UINT32 block,bool read)
{
	BufferInfo *node;
	BufferInfo *temp;

	//Increment the request Count everytime a block is accessed
	req_count++;

	try
	{
		BufferKey k(tblname, block);
		node =  bit.lookup(k);

		debug("\nlookup succeeded for %s %d", tblname.c_str(), block);
		if(mru != node)
		{
			//Rearrange the pointers - New
			//Detach node from current position
			node->prev->next = node->next;
			node->next->prev = node->prev;
			//Connect node to lru and mru
			node->next = mru->next;
			node->prev = mru;
			//Connect lru and mru to node
			mru->next = node;
			node->next->prev = node;

			//Rearrange the pointers - Old
			//temp = node->next;
			//temp->prev = node->prev;
			//node->next = mru->next; //Points to the LRU Node
			//mru->next = temp;
			//temp->prev = mru;
			//temp = node->prev;
			//node->prev = mru->prev;
			//mru->prev = temp;
			//temp->next = mru;
		}
	}
	catch(...)
	{
		if(mru->next->isfree)
		{
			node = mru->next;
			debug("\nfree node allocated for %s %d", tblname.c_str(), block);
		}
		else
		{
		//Miss counter is incremented only when a new block has to be 
		//brought in the memory

			debug("\nlookup failed for %s %d", tblname.c_str(), block);
				

			BufferInfo *victim = mru->next; // Victim is the LRU which is on the left/next of MRU
			if(victim->isdirty)
			{
				string vtblname = victim->tablename;
				write_file(vtblname, victim->block_num, victim->buffer);
			}
			BufferKey k(victim->tablename, victim->block_num);
			bit.remove(k);
			debug("\nvictimized %s, %d", victim->tablename, victim->block_num);
			node = victim;
		}
		if(read){
			read_file(tblname, block, node->buffer);
			//Increment the miss count
			miss_count++;
		}
	}

	//Update the MRU pointer
	mru = node;

	BufferKey k = BufferKey(tblname,block);
	node->tablename = new char[tblname.length() + 1];
	strcpy(node->tablename, tblname.c_str());
	node->block_num = block;
	node->isfree = false;	
	bit.insert( k, node);
	return node;	
}

//write to file
void BufferManager::write_file(string &tblname, UINT32 block_num, BYTE *data)
{
	BYTE *temp = NULL;
	int fd = -1;
	bool bDone = false;
	
	do
	{
		fd = open(dbname(tblname).c_str(), O_RDWR);
		if(fd <0)
			break;

		//This should also take care of inserting zero blocks
		//From 'man' page:
		// The lseek() function allows the file offset to be set beyond the end of
		// the existing end-of-file of the file. If data is later written at this
		// point, subsequent reads of the data in the gap return bytes of zeros
		// (until data is actually written into the gap)		
		if(lseek(fd, block_num * buffer_size, SEEK_SET) < 0) 
			break;

		temp = new BYTE[buffer_size];
		memcpy(temp,data,buffer_size);

		if(::write(fd, temp, buffer_size) < 0)
			break;
		bDone = true;
	} while(0);

	if(temp != NULL)
	{
		delete [] temp;		
	}

	if(fd > 0)
	{
		close(fd);		
	}
	
	if(!bDone)
		throw Exception(buffermgr_write_failed);
		
	external_commit(tblname, block_num);
}

//read file
//seek the block and read
void BufferManager::read_file(string &tblname, UINT32 block_num, BYTE *data)
{
	int fd = -1;
	bool bDone = false;
	do
	{
		fd = open(dbname(tblname).c_str(), O_RDONLY);
		if(fd <0)
			break;

		if(lseek(fd, block_num * buffer_size, SEEK_SET) < 0)
			break;

		if(::read(fd, data, buffer_size) < 0)
			break;
		
		bDone = true;

	} while(0);

	if(fd > 0)
	{
		close(fd);
	}
	if(!bDone)
		throw Exception(buffermgr_read_failed);
}

//To support the debug print, print buffer and print hit rate

inline bool caseInsCharCompareN(char a, char b) {
	return(toupper(a) == toupper(b));
}


bool caseInsCompare(const string& s1, const string& s2) {
	return((s1.size( ) == s2.size( )) &&
			equal(s1.begin( ), s1.end( ), s2.begin( ), caseInsCharCompareN));
}

void BufferManager::print(string what)
{
	string s1 = "PRINT BUFFER";
	string s2 = "PRINT HIT RATE";

	// Check what has to be printed
	if(caseInsCompare(what,s1))
	{
		//print the buffer
		//start from the first node

		BufferInfo *node = mru->next; 
		//Traverse the Buffer Info Table
		std::cout<<"\nbuf#\tfile\tblk#\n";
		std::cout<<"----\t----\t----\n";

		for(UINT32 i=0; i<num_blocks; i++)
		{
			std::cout<<i;
			if(node->isfree)
			{
				//buffer is not used
				std::cout<<"\t empty";
			}
			else
			{
				std::cout<<"\t"<<node->tablename;
				std::cout<<"\t"<<node->block_num;

			}
			node = node->next;
			std::cout << "\n";
		}
	}
	else if (caseInsCompare(what,s2))
	{
		//Print Hit Rate
		float hitrate;
		int hitcount;

		hitcount = req_count - miss_count;
		hitrate = float (hitcount) / float(req_count);

		std::cout<<"Hit Rate: \t"<<hitcount << " /" <<req_count <<
			" = "<<hitrate << std::endl;

	}

	return;

}
